﻿Imports System.Windows.Forms
Imports System.Drawing

''' <summary>
''' This class handles the basic layout of the main Pager Display window.
''' 
''' The class as two main functions. The first is to determine the initial
''' geometry for the main Pager Display window depending on type of Monitor
''' detected.  The second is to dynamically determine geometry and position 
''' of the individual GUI components such as the Border, LED Panel and the
''' individual LED Status Light Elements.
''' 
''' Additionally this class is responsible for creating the Status LED 
''' GUI Elements and calculating the correct font size to use. 
''' </summary>
''' <remarks>Only one instance of this class should be instantiated.</remarks>
Public Class LayoutManager

    ' -------------------------------------------------------------------------
    ' Member variables
    ' -------------------------------------------------------------------------
    '
    ' The Control String is used in calculating the appropriate font size.
    Private Const CONTROL_STRING As String = "8WO"

    ' Holds the instance of the main Pager Display window
    Private mPagerDisplay As PagerDisplay

    Private mDisplayMargin As Double = 0

    ' Hold the instance of the System LED GUI Element
    Private mSystemLED As Shape

    ' Hold the instance of the Network LED GUI Element
    Private mNetworkLED As Shape

    ' Hold the instance of the Page LED GUI Element
    Private mPageLED As Shape

    ' -------------------------------------------------------------------------
    ' Property Definitions
    ' -------------------------------------------------------------------------
    '
    ''' <summary>
    ''' The System Status LED UI Element
    ''' </summary>
    ''' <returns>A Shape representing the LED on the Display</returns>
    ''' <remarks>The System LED displays the over all status of the Pager System.</remarks>
    Public ReadOnly Property SystemLED As Shape
        Get
            Return mSystemLED
        End Get
    End Property

    ''' <summary>
    ''' The Network Status LED UI Element
    ''' </summary>
    ''' <returns>A Shape representing the LED on the Display</returns>
    ''' <remarks>The Network LED displays the over all status of the Network.</remarks>
    Public ReadOnly Property NetworkLED As Shape
        Get
            Return mNetworkLED
        End Get
    End Property

    ''' <summary>
    ''' The Page Status LED UI Element
    ''' </summary>
    ''' <returns>A Shape representing the LED on the Display</returns>
    ''' <remarks>The Page LED displays the over all status of the the Page codes read from file.</remarks>
    Public ReadOnly Property PageLED As Shape
        Get
            Return mPageLED
        End Get
    End Property


    ' -------------------------------------------------------------------------
    ' Main Code
    ' -------------------------------------------------------------------------

    ''' <summary>
    ''' Constructor.
    ''' </summary>
    ''' <param name="display">The PagerDisplay window to manage layout of.</param>
    Public Sub New(ByRef display As PagerDisplay)
        mPagerDisplay = display
        createLEDs()
    End Sub

    ''' <summary>
    ''' Performs initial configuration and layout of the Pager Display Window
    ''' </summary>
    ''' <remarks>Should be called once to perform initital setup.  Subsequent layouts
    ''' should be performed through the layoutPagerDisplay() method.</remarks>
    Public Sub configureMainDisplay()

        ' Determine main window geometry
        determineDisplayGeometry()
        ' Perform initial window layout
        layoutPagerDisplay()

    End Sub

    ''' <summary>
    ''' Determines what the initial Display window geometry should be.  The preference is
    ''' to locate an external, non-primary monitor on which to display the window full
    ''' screen without any border or control decorations. As an Overhead Paging System 
    ''' this is considered the normal operating mode.  If only the Primary monitor/screen
    ''' is found the system uses a standard window as defined in the XAML.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub determineDisplayGeometry()

#If DEBUG Then
        Debug.WriteLine("DEBUG MODE: Will not use external display.")
#Else

        ' Find the first no-primary monitor which should be the external monitor
        Dim display As Screen
        For Each display In Screen.AllScreens
            ' If an external display is found then open full screen on that display
            If Not display.Primary Then
                mPagerDisplay.Hide()
                mPagerDisplay.WindowStyle = WindowStyle.None
                mPagerDisplay.Top = display.Bounds.Top
                mPagerDisplay.Left = display.Bounds.Left
                mPagerDisplay.Height = display.Bounds.Height
                mPagerDisplay.Width = display.Bounds.Width
                mPagerDisplay.WindowState = FormWindowState.Maximized
                mPagerDisplay.Show()
                Exit For
            End If
        Next
#End If

        ' Register with resize events so we can perform necessary layouts.
        ' This should only be required if not running in the normal external monitor mode.
        AddHandler mPagerDisplay.SizeChanged, AddressOf windowSizeChangeHandler

    End Sub

    ''' <summary>
    ''' Performs a (re)layout of the Pager Display window.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub layoutPagerDisplay()

        Dim screenWidth As Double = mPagerDisplay.ActualWidth
        Dim screenHeight As Double = mPagerDisplay.ActualHeight

        Dim rctBorder As Shapes.Rectangle = mPagerDisplay.FindName("Border")
        Dim spnLEDArea As StackPanel = mPagerDisplay.FindName("LEDPanel")

        Debug.WriteLine("Screen Width:  " & screenWidth)
        Debug.WriteLine("Screen Hieght: " & screenHeight)

        Dim borderThickness As Double = screenHeight * 0.02
        rctBorder.StrokeThickness = borderThickness
        spnLEDArea.Height = borderThickness * 2.5
        spnLEDArea.Width = screenWidth * 0.33

        Dim ledBorder = spnLEDArea.Height * 0.2
        Dim ledHeight = spnLEDArea.Height - (2 * ledBorder)
        Dim ledWidth = (spnLEDArea.Width - (4 * ledBorder)) / 3

        sizeLED(mSystemLED, ledBorder, ledHeight, ledWidth)
        sizeLED(mNetworkLED, ledBorder, ledHeight, ledWidth)
        sizeLED(mPageLED, ledBorder, ledHeight, ledWidth)

        spnLEDArea.Children.Clear()
        spnLEDArea.Children.Add(mSystemLED)
        spnLEDArea.Children.Add(mNetworkLED)
        spnLEDArea.Children.Add(mPageLED)

        ' Recalculating Font Size can take a few seconds so push it into the background
        mPagerDisplay.Dispatcher.BeginInvoke(New Action(AddressOf determineFontSize))

    End Sub

    Private Sub sizeLED(ByRef led As Shape,
                        ByVal border As Double,
                        ByVal height As Double,
                        ByVal width As Double)
        led.Margin = New Thickness(border, border, 0, border)
        led.Height = height
        led.Width = width
        led.StrokeThickness = border * 0.25

    End Sub

    Private Sub windowSizeChangeHandler(ByVal sender As Object, ByVal e As SizeChangedEventArgs)
        layoutPagerDisplay()
    End Sub

    Private Sub createLEDs()
        mSystemLED = createLED()
        mNetworkLED = createLED()
        mPageLED = createLED()
    End Sub

    Private Function createLED() As Shapes.Rectangle
        Dim r = New Shapes.Rectangle()
        r.Stroke = New SolidColorBrush(Colors.Black)
        Return r
    End Function

    ''' <summary>
    ''' Dynamically calculate the new Font size based on current window geometry of the
    ''' main Pager Diplay
    ''' </summary>
    Private Sub determineFontSize()
        Dim fontFamily As String = mPagerDisplay.PageBlock.FontFamily.ToString
        Dim fontSize As Integer = 10
        Dim stepSize As Integer = 100
        Dim stringSize As SizeF = getStringSize(CONTROL_STRING, fontFamily, fontSize)

        Debug.WriteLine("Calculating font size.")

        ' First size up the font by increments of 100
        Do
            fontSize = fontSize + stepSize
            stringSize = getStringSize(CONTROL_STRING, fontFamily, fontSize)
            Debug.WriteLine("Considering: " & fontSize & "(" & stringSize.ToString & ")")
        Loop While (isFontTooSmall(stringSize))

        'once we've stepped over the size limt come down in increments of 10
        stepSize = 10
        Do
            fontSize = fontSize - stepSize
            stringSize = getStringSize(CONTROL_STRING, fontFamily, fontSize)
            Debug.WriteLine("Considering: " & fontSize & "(" & stringSize.ToString & ")")
        Loop While (isFontTooLarge(stringSize))

        ' Set the newly caluclate font size on the Page Code textblock UI Element
        mPagerDisplay.logger.info("Optimal font size calculated is " & fontSize)
        mPagerDisplay.PageBlock.FontSize = fontSize

    End Sub

    Private Function isFontTooLarge(ByVal stringSize As SizeF) As Boolean

        Return (stringSize.Width > getMaxWidth() Or
                 stringSize.Height > getMaxHeight())

    End Function

    Private Function isFontTooSmall(ByVal stringSize As SizeF) As Boolean

        Return (stringSize.Width < getMaxWidth() And
                 stringSize.Height < getMaxHeight())

    End Function

    Private Function getMaxWidth() As Double
        'The numeric multiplier offsets an issue with the Graphics.MeasureString
        '  method that calculates text sizing a bit smaller than when drawn.  
        '  To offset this we allow for more drawing width then we really have.
        Return mPagerDisplay.ActualWidth * (1.25 - mDisplayMargin)
    End Function

    Private Function getMaxHeight() As Double
        'The numeric multiplier offsets an issue with the Graphics.MeasureString
        '  method that calculates text sizing a bit smaller than when drawn.  
        '  To offset this we allow for more drawing height then we really have.
        Return mPagerDisplay.ActualHeight * (1.5 - mDisplayMargin)
    End Function

    Private Function getStringSize(ByVal text As String,
                           ByVal fontFamily As String,
                           ByVal fontSize As Integer) As SizeF

        Dim img As New Bitmap(1, 1)
        Dim g As Graphics = Graphics.FromImage(img)
        g.PageUnit = GraphicsUnit.Pixel
        g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias

        Dim f As New Font(fontFamily, fontSize)
        Dim stringSize As SizeF = g.MeasureString(text, f, 2000, StringFormat.GenericTypographic)
        g.Dispose()
        img.Dispose()
        f.Dispose()
        Return stringSize

    End Function


End Class
